/*******************************************************************************
 * Copyright (c) 2009 IBM Corporation and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aseclipse.osgi.classes
{
    import mx.collections.ArrayCollection;
    import mx.core.Application;
    
    import org.aseclipse.osgi.Bundle;
    import org.aseclipse.osgi.BundleEvent;
    
    public class BundleStartService
    {

        private var startingBundleQueue:Array = new Array();	
        private var startingQueueBusy:Boolean = false;
        private var startingBundleMap:Object = new Object();    
        private var framework:BundleFrameworkImpl;
        public function BundleStartService(frame:BundleFrameworkImpl)
        {
            framework = frame;
            framework.getBundleDispatcher().addEventListener(BundleEvent.BUNDLECHANGED, bundleChangeHandler);
        }

        /****
        * Listening on bundle changes of Installed/Resolved
        * If a bundle A is installed and bundle B is in starting queue, also A extends B, then check B is resolveable, 
        *     if yes, then start it
        * If a bundle A is started and bundle B is in starting queue, also B requires A, then check B is startable, 
        *    if yes, then start B. 
        */ 
        private function bundleChangeHandler(event:BundleEvent):void {
            var state:int = event.getState();
            var bundle:BundleImpl = event.getBundle() as BundleImpl;
            var id:String = bundle.getSymbolicName();
            // If a bundle is installed, and we need to check whether it is in 
            // resolving queue, if it is, then check it can be resolved.
            if(event.getState() == BundleEvent.STARTED) {
                trace("[BundleStartService.bundleChangeHandler] "+id+" is started ");
                if(containBundle(id) == true) {  // remove its task from queue
                    removeTask(id);
                }
                var array:Array = framework.getDependentBundles(id);
                for each(var item:String in array) {
                    if(containBundle(item) == true) {
                        var bund:BundleImpl = framework.getBundle(item) as BundleImpl;
                        if(framework.isStartable(item) == true) {
                            trace("[BundleStartService.bundleChangeHandler] "+id+"'s dependent bundle "+item+" is startable, start it ");
                            bund.start();
                        } else {
                            // if extender bundle is not installed, then install it
                            if(bund.extendersReady == false && bund.extendersLoading == false) {
                                framework.installExtenderBundles(item);
                            }
                        }
                    }
                }
            } else if(state == BundleEvent.EXTENDERS_INSTALLED) {
                trace("[BundleStartService.bundleChangeHandler] "+id+" extenders are installed");    
                if(framework.isStartable(id) == true) {
                    trace("[BundleStartService.bundleChangeHandler] "+id+" is startable, start it ");
                    bundle.start();
                }
            } else if(state == BundleEvent.RESOLVED) {
                trace("[BundleStartService.bundleChangeHandler] "+id+" is resolved ");
                if(containBundle(id) == true) {
                    // just resolve, then start it
                    if(bundle.hasRequires() == true && bundle.requiresStarting == false) {
                        bundle.requiresStarting = true;
                        framework.startRequireBundles(id);
                    }
                    if(bundle.hasExtensionPoint() == true && bundle.extendersLoading == false && bundle.extendersReady == false) {
                        framework.installExtenderBundles(id);
                    }
                    if(framework.isStartable(id) == true) {
                        bundle.start();
                    }
                }
            } 
        }
        
        private function containBundle(id:String):Boolean {
            return startingBundleMap[id] != null;
        }
        
        /********
        * Some bundle is resolved, 
        * remove it's task from queue
        */ 
        private function removeTask(id:String):void {
            trace("remove bundle "+id + " from starting task");
            if(startingQueueBusy == false) {
                startingQueueBusy = true;
                var list:ArrayCollection = new ArrayCollection(startingBundleQueue);
                var resolveableList:Array = new Array();
                for(var i:int=0;i<list.length;i++) {
                    var task:Object = list.getItemAt(i);
                    var pluginId:String = task.pluginId as String;
                    if(pluginId == id) {
                        list.removeItemAt(i);
                        startingBundleMap[id] = null;
                        break;
                    }
                }
                startingQueueBusy = false;    
                if(startingBundleQueue.length == 0) {
                    framework.notifyEmptyStartQueue();
                }
            } else { // wait unitl lock is release
                Application.application.callLater(removeTask, [id]);
            }
        }
                
        public function start(task:Object):void {
            if(containBundle(task.pluginId)) {
                trace(task.pluginId + " is already in the starting list");
                run();
                return;
            }
            // write lock on startingBundleQueue
            if(startingQueueBusy == false) {
                startingQueueBusy = true;
                startingBundleQueue.push(task);
                var plugin:String = task.pluginId as String;
                startingBundleMap[plugin] = plugin;
                startingQueueBusy = false;    
                run();
            } else {
                Application.application.callLater(start, [task]);
            }
        }
        
        // read lock on startingBundleQueue
        protected function run():void {
            if(startingBundleQueue.length > 0 && startingQueueBusy == false) {
                startingQueueBusy = true;
                // trigger one by one
                var list:ArrayCollection = new ArrayCollection(startingBundleQueue);
                for (var i:int =0 ; i<list.length; i++) {
                    var task:Object = list.getItemAt(i) as Object;
                    var id:String = task.pluginId;
                    var bundle:BundleImpl = framework.getBundle(id) as BundleImpl;
                    // doesn't install it, wait
                    if(bundle == null) {
                        trace("[run] "+id+" is not installed, ignore...");
                        // wait
                    } else { // already install, but need to check resolveable
                        if(bundle.getState() == Bundle.RESOLVED) {
                            if(bundle.hasRequires() == true && bundle.requiresStarting == false) {
                                bundle.requiresStarting = true;
                                Application.application.callLater(framework.startRequireBundles, [id]);
                                trace("[run] "+id+" start required bundles");
                            }
                            if(bundle.hasExtensionPoint() == true && bundle.extendersLoading == false && bundle.extendersReady == false) {
                                framework.installExtenderBundles(id);
                                trace("[run] "+id+" install extender bundles"); 
                            }
                            if(framework.isStartable(id) == true) {
                                bundle.start();
                            }
                        }
                    }
                }
                startingQueueBusy = false;
            } else {
                trace("[run] service is busy...");
            }
        }
    }
}